package mmc.cell;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.Serializable;
import java.util.zip.DataFormatException;
import java.util.zip.Deflater;
import java.util.zip.Inflater;

import mmc.Constants;
import mmc.EngineObject;
import mmc.Settings;

@SuppressWarnings("serial")
public class ServerCell implements Comparable<ServerCell>, Serializable{
	public static final int CELL_NOT_COMPRESSED = Integer.MIN_VALUE;
	public CellPosition pos;
	public byte[] block;
	public int solid = CELL_NOT_COMPRESSED;
	
	public ServerCell(){
		pos = new CellPosition(Integer.MIN_VALUE,Integer.MIN_VALUE,Integer.MIN_VALUE);
	}
	
	public ServerCell(int x, int y, int z, byte[] content) {
		this(new CellPosition(x,y,z), content);
	}
	
	public ServerCell(CellPosition pos, byte[] content) {
		this.pos = pos;
		block = content;
		solid = CELL_NOT_COMPRESSED;
	}
	
	public ServerCell(int x, int y, int z, int solid) {
		this(new CellPosition(x,y,z), solid);
	}
	
	public ServerCell(CellPosition pos, int solid){
		this.pos = pos;
		block = null;
		this.solid = solid;
	}
	
	public ServerCell(CellPosition pos){
		this.pos = pos;
		block = new byte[Constants.CELL_SIZE3];
		this.solid = CELL_NOT_COMPRESSED;
	}
	
	public boolean isCompressed(){
		return solid!=CELL_NOT_COMPRESSED;
	}
	
	public boolean isCompressedAndSolid(){
		return solid!=CELL_NOT_COMPRESSED && Block.isSolid((byte)solid);
	}
	
	public boolean isBlockInRange(EngineObject o, float maxDistance, int bx, int by, int bz){
		float s = Constants.BLOCK_HALF_DIAGONAL + maxDistance;
		
		float dx = (pos.x + bx + 0.5f)-o.x;
		dx *= dx;
		float dy = (pos.y + by + 0.5f)-o.y;
		dy *= dy;
		float dz = (pos.z + bz + 0.5f)-o.z;
		dz *= dz;
		
		return (dx+dy+dz) < s*s;
	}
	
	public boolean isContainingBlockInRange(EngineObject o, float maxDistance){
		float s = Constants.CELL_HALF_DIAGONAL + maxDistance;
		
		float dx = pos.x + Constants.CELL_SIZE_HALF - o.x;
		dx *= dx;
		float dy = pos.y + Constants.CELL_SIZE_HALF - o.y;
		dy *= dy;
		float dz = pos.z + Constants.CELL_SIZE_HALF - o.z;
		dz *= dz;
		
		return (dx+dy+dz) < s*s;
	}

	@Override
	public int compareTo(ServerCell o) {
		return pos.compareTo(o.pos);
	}

	public boolean equals(ServerCell c) {
		return pos.equals(c.pos);
	}

	@Override
	public String toString() {
		return pos.toString();
	}
	
	public void compress(){
		if(solid==CELL_NOT_COMPRESSED){
			byte b = block[0];
			
			for(int i=1;i<Constants.CELL_SIZE3;i++){
				if(block[i]!=b)
					return;
			}
			
			solid = b;
			block = null;
		}
	}
	
	public void decompress() {
		if(solid != CELL_NOT_COMPRESSED){
			block = new byte[Constants.CELL_SIZE3];
			
			for(int i=0;i<Constants.CELL_SIZE3;i++){
				block[i] = (byte) solid;
			}
			
			solid = CELL_NOT_COMPRESSED;
		}
	}
	
	public byte getValue(int x, int y, int z){
		if(solid!=CELL_NOT_COMPRESSED)
			return (byte)solid;
		else
			return block[x*Constants.CELL_SIZE2+y*Constants.CELL_SIZE+z];
	}
	
	public void setValue(int x, int y, int z, byte value){
		if(isCompressed())
			decompress();
		
		block[x*Constants.CELL_SIZE2+y*Constants.CELL_SIZE+z] = value;
	}
	
	private void writeObject(java.io.ObjectOutputStream out) throws IOException{
		out.writeUnshared(pos);
		
		out.writeInt(solid);
		
		if(!isCompressed()){
			byte[] compressedData = null;
			boolean compressedTransmission = false;
			
			if(Settings.CLIENT_UPDATE_COMPRESSION){
				Deflater d = new Deflater(Settings.CLIENT_UPDATE_COMPRESSION_LEVEL);
				d.setInput(block);
				d.finish();
				
				ByteArrayOutputStream bos = new ByteArrayOutputStream(block.length);
	
				// Compress the data
				byte[] buf = new byte[1024];
				while (!d.finished()) {
				    int count = d.deflate(buf);
				    bos.write(buf, 0, count);
				}
				try {
				    bos.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
	
				// Get the compressed data
				compressedData = bos.toByteArray();
				compressedTransmission = compressedData.length<block.length;
			}
			
			out.writeBoolean(compressedTransmission);
			
			if(compressedTransmission){
				out.writeUnshared(compressedData);
				
				if(Settings.CLIENT_TRACE_CHUNK_TRANSMISSION){
					System.out.println("Sending compressed ("+(100*compressedData.length/(float)block.length)+" %) chunk");
				}
			}else{
				if(Settings.CLIENT_TRACE_CHUNK_TRANSMISSION){
					System.out.println("WARNING: sending uncompressed chunk!");
				}
				out.writeUnshared(block);
			}
		}
	}

	private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException{
		pos = (CellPosition) in.readObject();

		solid = in.readInt();
		if(solid == CELL_NOT_COMPRESSED){
			if(in.readBoolean()){
				byte[] compressed = (byte[])in.readObject();
				Inflater i = new Inflater();
				i.setInput(compressed);
				
				ByteArrayOutputStream bos = new ByteArrayOutputStream(compressed.length);
		
				// Decompress the data
				byte[] buf = new byte[1024];
				while (!i.finished()) {
				    try {
				        int count = i.inflate(buf);
				        bos.write(buf, 0, count);
				    } catch (DataFormatException e) {
				    	e.printStackTrace();
				    }
				}
				try {
				    bos.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
				block = bos.toByteArray();
			}else{
				block = (byte[])in.readObject();
			}
		}
	}
}
